#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "buffer.h"
#include "undo.h"
#include "list.h"
#include "tabmap.h"
#include "window.h"

static const int INIT_BUF_CAP = 256;

static void buf_double_cap(buf_t *buf)
{
    buf->cap = (buf->cap - 1) * 2 + 1;
    if ((buf->data = realloc(buf->data, buf->cap)) == NULL) {
	fputs("Out of memory", stderr);
	exit(1);
    }
}

buf_t * buf_new(void)
{
    buf_t *buf = NULL;
    char *data = NULL;

    if ((buf = (buf_t *)malloc(sizeof(buf_t))) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    if ((data = (char *)malloc(INIT_BUF_CAP + 1)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }

    data[0] = '\0';
    buf->data = data;
    buf->len = 0;
    buf->cap = INIT_BUF_CAP;
    buf->tabmap = tabmap_new();
    buf->last_modified_pos = 0;

    INIT_LIST_HEAD(&(buf->list));

    return buf;
}

void buf_free(buf_t *buf)
{
    list_del(&(buf->list));
    free(buf->data);
    free(buf->tabmap);
    free(buf);
}

buf_t * buf_prev(buf_t *buf)
{
    if (buf == NULL)
	buf = current_window->buf;

    if (buf == current_window->head_buf)
	return NULL;

    return list_entry(buf->list.prev, buf_t, list);
}

buf_t * buf_next(buf_t *buf)
{
    if (buf == NULL)
	buf = current_window->buf;

    if (buf == current_window->tail_buf)
	return NULL;

    return list_entry(buf->list.next, buf_t, list);
}

buf_t * buf_next_circular(buf_t *buf)
{
    if (buf == NULL)
	buf = current_window->buf;

    return list_entry(buf->list.next, buf_t, list);
}


int buf_inserts_noundo(buf_t *buf, char *data, int pos)
{
    int len = strlen(data);

    if(pos < 0 || pos > buf->len)
	return -1;

    current_window->is_saved = FALSE;
    if ((buf->len + len) < buf->cap) {
	char ch = buf->data[pos];
	memmove(buf->data + pos + len, buf->data + pos, 
		strlen(buf->data + pos) + 1);
	strcpy(buf->data + pos, data);
	buf->data[pos + len] = ch;
	buf->len += len;
	buf->last_modified_pos = pos + len;
	current_window->buf = buf;
	/* add by Devin */
	if(len <= 20){
		tabmap_insert_str(buf->tabmap, pos, data);
	}else{
		free(buf->tabmap->map);
		free(buf->tabmap);
		buf->tabmap = tabmap_get_tabmap(buf->data);
	}
	/* end add */
    } else {
	buf_double_cap(buf);
	buf_inserts_noundo(buf, data, pos);
    }

    return len;
}

int buf_inserts(buf_t *buf, char *data, int pos)
{
    int len = strlen(data);
    char *data_copy = NULL;
    
    if (buf_inserts_noundo(buf, data, pos) == -1)
	return -1;

    if ((data_copy = (char *)malloc(len + 1)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    strcpy(data_copy, data);

    undo_cmd_add(BUF_DELETE_ACTION, buf, data_copy, pos, pos + len, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return len;
}

int buf_insertc_noundo(buf_t *buf, char ch, int pos)
{
    char data[2] = {0};

    current_window->is_saved = FALSE;
    data[0] = ch;
    return buf_inserts_noundo(buf, data, pos);
}

int buf_insertc(buf_t *buf, char ch, int pos)
{
    char *data = NULL;

    if (buf_insertc_noundo(buf, ch, pos) == -1)
	return -1;
    
    if ((data = (char *)malloc(2)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    data[0] = ch;
    data[1] = '\0';

    undo_cmd_add(BUF_DELETE_ACTION, buf, data, pos, pos + 1, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return 1;
}

int buf_appends_noundo(buf_t *buf, char *data)
{
    return buf_inserts_noundo(buf, data, buf->len);
}

int buf_appends(buf_t *buf, char *data)
{
    return buf_inserts(buf, data, buf->len);
}

int buf_appendc_noundo(buf_t *buf, char ch)
{
    return buf_insertc_noundo(buf, ch, buf->len);
}

int buf_appendc(buf_t *buf, char ch)
{
    return buf_insertc(buf, ch, buf->len);
}

int buf_delete_noundo(buf_t *buf, int start, int end)
{
    int len = end - start;

    if (len < 0 || len > buf->len || 
	end > buf->len || start < 0)
	return -1;

    if (len == 0)
	return 0;

    current_window->is_saved = FALSE; 
    memmove(buf->data + start, buf->data + end, 
	    strlen(buf->data + end) + 1);
    buf->len -= len;
    buf->last_modified_pos = start;
    current_window->buf = buf;
    /* add by Devin */
    if(len <= 20){
        tabmap_delete_str(buf->tabmap, start, len);
    }else{
        free(buf->tabmap->map);
	free(buf->tabmap);
        buf->tabmap = tabmap_get_tabmap(buf->data);
    }
    /* end add */
    return len;
}

int buf_delete(buf_t *buf, int start, int end)
{
    char *data = NULL;
    int len = end - start;
    int rv = 0;

    if ((data = (char *)malloc(len + 1)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    strncpy(data, buf->data + start, len);
    data[len] = '\0';
   
    if ((rv = buf_delete_noundo(buf, start, end)) == -1) {
	free(data);
	return -1;
    }

    undo_cmd_add(BUF_INSERTS_ACTION, buf, data, start, end, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return rv;
}


int buf_replace_noundo(buf_t *buf, int start, int end, char *data)
{
    buf_delete_noundo(buf, start, end);
    current_window->is_saved = FALSE;
    return buf_inserts_noundo(buf, data, start);
}

int buf_replace(buf_t *buf, int start, int end, char *data)
{
    char *data_old = NULL;
    int len = end - start;
    int rv = 0;

    if ((data_old = (char *)malloc(len + 1)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    strncpy(data_old, buf->data + start, len);
    data_old[len] = '\0';

    if ((rv = buf_replace_noundo(buf, start, end, data)) == -1) {
	free(data_old);
	return -1;
    }

    undo_cmd_add(BUF_REPLACE_ACTION, buf, data_old, 
		 start, start + strlen(data), current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return rv;
}

int buf_delete_buf_noundo(buf_t *buf, int merge)
{
    buf_t *prev_buf = buf_prev(buf);

    current_window->is_saved = FALSE;
    if (current_window->head_buf != current_window->tail_buf) {
	if (buf == current_window->head_buf) {
	    current_window->head_buf = buf_next(current_window->head_buf);
	}
	if (buf == current_window->tail_buf) {
	    current_window->tail_buf = buf_prev(current_window->tail_buf);
	}
    }

    if (prev_buf != NULL) {
	int len = 0;
	if (merge == TRUE) {
	    len = buf_appends_noundo(prev_buf, buf->data);
	}
	buf->list.prev->next = buf->list.next;
	buf->list.next->prev = buf->list.prev;
	prev_buf->last_modified_pos = prev_buf->len - len;
	current_window->buf = prev_buf;
	return len;
    } else {
	if (merge == FALSE) {
	    current_window->head_buf = buf_next(buf);
	    buf->list.prev->next = buf->list.next;
	    buf->list.next->prev = buf->list.prev;
	    current_window->buf = current_window->head_buf;
	    if (current_window->buf != NULL) {
		current_window->buf->last_modified_pos = 0;
		return current_window->buf->len;
	    } else {
		return 0;
	    }
	} else {
	    return 0;
	}
    }
}

int buf_delete_buf(buf_t *buf)
{
    int rv = buf_delete_buf_noundo(buf, TRUE);
    if (rv > 0)
	undo_cmd_add(BUF_RESTORE_BUF_ACTION, buf, 
	buf->data, 0, buf->len, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return rv;
}

int buf_remove_buf(buf_t *buf)
{
    int rv = buf_delete_buf_noundo(buf, FALSE);
    current_window->is_saved = FALSE;
    undo_cmd_add(BUF_RESTORE_BUF_ACTION, buf, 
		 buf->data, 0, 0, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
    return rv;
}

buf_t * buf_copy_buf(buf_t *buf)
{
    buf_t *buf_copy = buf_new();
    char *data = NULL;

    if ((data = (char *)malloc(buf->cap + 1)) == NULL) {
	perror("Out of memory!");
	exit(1);
    }
    strncpy(data, buf->data, buf->len);
    data[buf->len] = '\0';

    buf_copy->data = data;
    buf_copy->len = buf->len;
    buf_copy->cap = buf->cap;
    buf_copy->tabmap = tabmap_copy_tabmap(buf->tabmap);
    buf_copy->last_modified_pos = buf->last_modified_pos;
    buf_copy->list = buf_copy->list;

    return buf_copy;
}

void buf_create_buf_noundo(int offset)
{  
    current_window->is_saved = FALSE;
    if (current_window->buf == NULL) { /* The first time */
	current_window->buf = buf_new();
	current_window->head_buf = current_window->buf;
	current_window->tail_buf = current_window->buf;
    } else {
	buf_t *buf = buf_new();
	buf_t *old_buf = current_window->buf;
	list_add(&(buf->list), &(old_buf->list));
	buf_inserts_noundo(buf, old_buf->data + offset, 0);
	buf_delete_noundo(old_buf, offset, old_buf->len);
	current_window->buf = buf;
	current_window->tail_buf = list_entry(current_window->head_buf->list.prev, buf_t, list);
    }
}

void buf_create_buf(int offset)
{
    buf_create_buf_noundo(offset);
    undo_cmd_add(BUF_DELETE_BUF_ACTION, current_window->buf, 
		 current_window->buf->data, 0, offset, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
}

void buf_restore_buf_noundo(buf_t *buf, int split)
{
    buf_t *prev_buf = buf_prev(buf);
    int len = 0;
    
    current_window->is_saved = FALSE;
    if (split == TRUE) {
	len = buf->len;
    }
    
    if (buf == prev_buf) {
	current_window->head_buf = buf;
    }

    if (prev_buf != NULL) {
	buf_delete_noundo(prev_buf, prev_buf->len - len, prev_buf->len);	
    }

    buf->list.prev->next = &(buf->list);
    buf->list.next->prev = &(buf->list);
    
    buf->last_modified_pos = 0;
    current_window->buf = buf;
    current_window->tail_buf = list_entry(current_window->head_buf->list.prev, buf_t, list);
}

void buf_insert_buf_noundo(buf_t *buf, buf_t *prev_buf)
{
    current_window->is_saved = FALSE;
    if (prev_buf == NULL) {
	prev_buf = current_window->tail_buf;
	current_window->head_buf = buf;
    }

    buf->list.next = prev_buf->list.next;
    buf->list.prev = &(prev_buf->list);
    prev_buf->list.next->prev = &(buf->list);
    prev_buf->list.next = &(buf->list);

    current_window->buf = buf;

    current_window->tail_buf = list_entry(current_window->head_buf->list.prev, buf_t, list);
}

void buf_insert_buf(buf_t *buf, buf_t *prev_buf)
{
    buf_insert_buf_noundo(buf, prev_buf);
    undo_cmd_add(BUF_REMOVE_BUF_ACTION, current_window->buf, 
		 current_window->buf->data, 0, 0, current_window->num_cmds);
    if (current_window->multiple == TRUE)
	current_window->num_cmds++;
    undo_cmd_clear_cmds(current_window->redo_cmd);
}

void buf_start_cmds(void)
{
    current_window->multiple = TRUE;
}

void buf_finish_cmds(void)
{
    current_window->multiple = FALSE;
    current_window->num_cmds = 1;
}

void buf_current_prev(void)
{
    current_window->buf = buf_prev(current_window->buf);
}

void buf_current_next(void)
{
    current_window->buf = buf_next(current_window->buf);
}

void buf_to_head(void)
{
    current_window->buf = current_window->head_buf;
}

void buf_to_tail(void)
{
    current_window->buf = current_window->tail_buf;
}

buf_t * buf_head_buf(void)
{
    return current_window->head_buf;
}

void buf_clear_bufs(buf_t *buf_head)
{
    struct list_head *pos;
    struct list_head *n;
   
    if (buf_head == NULL)
	return;

    list_for_each_safe(pos, n, &(buf_head->list)) {
	buf_t *buf = list_entry(pos, buf_t, list);
	buf_free(buf);
    }
}
